﻿using System;
using System.IO;
using Ionic.Zip;
using mki.Backup.Helper;
using mki.Backup.Helper.Extensions;
using mki.Backup.Objects;

namespace mki.Backup.Interfaces
{
	/// <summary>
	/// Zip Job
	/// </summary>
	internal class ZipJob
	{
		#region Properties (1)

		/// <summary>
		/// Gets or sets a value indicating whether [success full].
		/// </summary>
		/// <value>
		///   <c>true</c> if [success full]; otherwise, <c>false</c>.
		/// </value>
		public static Boolean Successfull { get; set; }

		#endregion Properties

		#region Methods (5)

		// Public Methods (1) 

		/// <summary>
		/// Runs the job.
		/// </summary>
		/// <param name="job">The job.</param>
		/// <returns>
		/// True if job was executed successfully otherwise false if job failed
		/// </returns>
		public static Boolean RunJob(ZipJobElement job)
		{
			if (String.IsNullOrWhiteSpace(job.TargetFolderAndFileName)) { throw new ArgumentException("targetFolderAndFileName is null or empty!"); }
			var targetFolderAndFileName = job.TargetFolderAndFileName.ReplaceTokens();
			if (!Directory.Exists(Path.GetDirectoryName(targetFolderAndFileName))) { throw new DirectoryNotFoundException(String.Format("Direcotry '{0}' not found", Path.GetDirectoryName(targetFolderAndFileName))); }

			try
			{
				using (var zip = new ZipFile())
				{
					// add
					foreach (ZipAddFileElement file in job.AddFileCollection)
					{	// files
						try { addFile(file, zip); }
						catch (Exception ex)
						{
							Logging.WriteWarning("cannot add file '{0}': {1}\n{2}", file.Source.ReplaceTokens(), ex.Message, ex.StackTrace);
							Successfull = false;
						}
					}
					foreach (ZipAddFolderElement folder in job.AddFolderCollection)
					{	// folders
						try { addFolder(folder, zip); }
						catch (Exception ex)
						{
							Successfull = false;
							Logging.WriteWarning("cannot add folder '{0}': {1}\n{2}", folder.Source, ex.Message, ex.StackTrace);
						}
					}

					zip.Comment = String.Format("Zipfile was created in job '{0}' on {1}", job.Name, DateTime.Now);
					Logging.WriteInformation("Saving zipfile '{0}'", targetFolderAndFileName);
					zip.Save(targetFolderAndFileName);
				}

				// if we came here we can start to delete
				if (File.Exists(targetFolderAndFileName))
				{
					foreach (ZipAddFileElement file in job.AddFileCollection)
					{
						try { deleteFile(file, targetFolderAndFileName); }
						catch (Exception ex)
						{
							Successfull = false;
							Logging.WriteWarning("cannot delete file '{0}': {1}\n{2}", file.Source.ReplaceTokens(), ex.Message, ex.StackTrace);
						}
					}
					foreach (ZipAddFolderElement folder in job.AddFolderCollection)
					{
						try { deleteFolder(folder, targetFolderAndFileName); }
						catch (Exception ex)
						{
							Successfull = false;
							Logging.WriteWarning("cannot delete folder '{0}': {1}\n{2}", folder.Source, ex.Message, ex.StackTrace);
						}
					}
				}

				Successfull = true;
			}
			catch (Exception ex)
			{
				Logging.WriteError("while creating zip file '{0}': {1}\n{2}", job.Name, ex.Message, ex.StackTrace);
				Successfull = false;
			}

			return Successfull;
		}
		// Private Methods (4) 

		/// <summary>
		/// Adds the file.
		/// </summary>
		/// <param name="fileElement">The file element.</param>
		/// <param name="zipFile">The zip file.</param>
		private static void addFile(ZipAddFileElement fileElement, ZipFile zipFile)
		{
			if (fileElement.Source.IsNullOrEmpty()) { throw new ArgumentException("SourceFile is null or empty!"); }
			var source = fileElement.Source.ReplaceTokens();
			if (!File.Exists(source)) { throw new FileNotFoundException(String.Format("File '{0}' not found", source)); }

			Logging.WriteVerbose("Adding file '{0}' to zip", source);

			zipFile.AddFile(source, String.Empty);
		}

		/// <summary>
		/// Adds the file.
		/// </summary>
		/// <param name="folderElement">The folder element.</param>
		/// <param name="zipFile">The zip file.</param>
		private static void addFolder(ZipAddFolderElement folderElement, ZipFile zipFile)
		{
			if (folderElement.Source.IsNullOrEmpty()) { throw new ArgumentException("SourceFolder is null or empty!"); }
			if (!Directory.Exists(folderElement.Source)) { throw new DirectoryNotFoundException(String.Format("Direcotry '{0}' not found", folderElement.Source)); }

			Logging.WriteVerbose("Adding folder '{0}' to zip", folderElement.Source);

			// save with last directory in zip
			zipFile.AddDirectory(folderElement.Source, new DirectoryInfo(folderElement.Source).Name);
		}

		/// <summary>
		/// Deletes the file.
		/// </summary>
		/// <param name="fileElement">The file element.</param>
		/// <param name="targetFolderAndFileName">Name of the target folder and file.</param>
		private static void deleteFile(ZipAddFileElement fileElement, String targetFolderAndFileName)
		{
			var source = fileElement.Source.ReplaceTokens();
			if (!fileElement.Delete || source == targetFolderAndFileName) { return; }

			if (fileElement.Source.IsNullOrEmpty()) { throw new ArgumentException("SourceFile is null or empty!"); }
			if (!File.Exists(source)) { throw new FileNotFoundException(String.Format("File '{0}' not found", source)); }

			Logging.WriteVerbose("Deleting files '{0}'", source);
			try { File.Delete(source); }
			catch (Exception ex) { Logging.WriteError("could not delete file '{0}': {1}\n{2}", source, ex.Message, ex.StackTrace); }
		}

		/// <summary>
		/// Deletes the folder.
		/// </summary>
		/// <param name="folderElement">The folder element.</param>
		/// <param name="targetFolderAndFileName">Name of the target folder and file.</param>
		private static void deleteFolder(ZipAddFolderElement folderElement, String targetFolderAndFileName)
		{	// need to delete?
			if (!folderElement.Delete) { return; }

			if (folderElement.Source.IsNullOrEmpty()) { throw new ArgumentException("SourceFolder is null or empty!"); }
			if (!Directory.Exists(folderElement.Source)) { throw new DirectoryNotFoundException(String.Format("Direcotry '{0}' not found", folderElement.Source)); }

			Logging.WriteVerbose("Deleting folders and files in '{0}'", folderElement.Source);
			try
			{
				// get files in folder and delete them folder will not be deleted
				var directory = new DirectoryInfo(folderElement.Source);
				var files = directory.GetFiles("*.*", SearchOption.AllDirectories);
				foreach (var file in files)
				{
					if (file.FullName == targetFolderAndFileName) { continue; }

					try { file.Delete(); }
					catch (Exception ex) { Logging.WriteError("could not delete file '{0}': {1}\n{2}", file.Name, ex.Message, ex.StackTrace); }
				}

				// 2011-05-07: disabled deleting of folder structure -> create enum for delete behaviour
				//// now try to delete subfolders
				//directory.Refresh();
				//var folders = directory.GetDirectories("*.*", SearchOption.AllDirectories);
				//foreach (var folder in folders) { folder.Delete(); }
			}
			catch (Exception ex)
			{
				Logging.WriteError("could not delete folder '{0}': {1}\n{2}", folderElement.Source, ex.Message, ex.StackTrace);
			}
		}

		#endregion Methods
	}
}